package com.melichercik.app;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.StreamCorruptedException;

import com.example.bcapp.R;
import com.melichercik.code.*;


import android.graphics.Canvas;
import android.media.MediaPlayer;
import android.media.MediaPlayer.OnCompletionListener;
import android.os.Bundle;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.util.Log;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.View.OnTouchListener;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.ScrollView;
import android.widget.TextView;
import android.widget.Toast;

import orbotix.robot.base.OrbBasicControl;
import orbotix.robot.base.RobotProvider;
import orbotix.sphero.Sphero;

public class MainActivity extends Activity implements OnTouchListener {
	
	GlobalState gs;
	
	private Activity activity = this;
	
	private Code code = new Code();
	
	RenderView view;
	Sphero sphero;
	
	private int menuShow;
	
	private Brick actionBrick;
	private int addBrickIndex;
	
	private boolean move = false;
	private boolean click = false;
	
	private int offsetX = 50;
	private int offsetY = 50;
	private int deltaX;
	private int deltaY;
	private int lastX;
	private int lastY;
	private int deltaTouchDistance;
	private int scale = 200;
	
	String selected = "";
	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		gs = (GlobalState) getApplication();
		
		super.onCreate(savedInstanceState);
		
		view = new RenderView(this);
		view.setOnTouchListener(this);
		setContentView(view);
		
		registerForContextMenu(view);
		
		view.resume();
		
		rawLoadProgram("auto_save");
		
		if (gs.robot != null) {
			sphero = gs.robot;
			
			sphero.getOrbBasicControl().addEventListener(new OrbBasicControl.EventListener() {
                @Override
                public void onEraseCompleted(boolean success) {
                    Toast.makeText(activity, "Program started", Toast.LENGTH_SHORT).show();
                }

                @Override
                public void onLoadProgramComplete(boolean success) {
//                    Toast.makeText(activity, "Load", Toast.LENGTH_SHORT).show();
                }

                @Override
                public void onPrintMessage(String message) {
                	message = message.replace("\n", "");
                	
                	if (message.contains("playSoundMessage")) {
                		playSound();
                	} else {
                		Toast.makeText(activity, message, Toast.LENGTH_SHORT).show();
                	}
                }

                @Override
                public void onErrorMessage(String message) {
                    Log.d("ERROR", message);
//                    Toast.makeText(activity, message, Toast.LENGTH_LONG).show();
                }

                @Override
                public void onErrorByteArray(byte[] bytes) {
//                	Toast.makeText(activity, "ErrorByte", Toast.LENGTH_SHORT).show();
                }
            });
		}
	}
	
	@Override
	protected void onPause() {
		// TODO Auto-generated method stub
		super.onPause();
		
		view.pause();
		
		rawSaveProgram("auto_save");
		
//		RobotProvider.getDefaultProvider().disconnectControlledRobots();
		
	}
	
	@Override
	protected void onRestart() {
		// TODO Auto-generated method stub
		super.onRestart();
		view.resume();
	}
	
	private void executeCode() {
		String progString = code.compileCode();
		
		if (!checkSpheroConnection()) {
			return;
		}
		
		sphero.getOrbBasicControl().eraseStorage();
		
        byte[] prog = progString.getBytes();
        sphero.getOrbBasicControl().setProgram(prog);
        
        sphero.getOrbBasicControl().loadProgram();
        sphero.getOrbBasicControl().executeProgram();
	}
	
	private void abortExecuting() {
		if (!checkSpheroConnection()) {
			return;
		}
		
		sphero.getOrbBasicControl().abortProgram();
		sphero.getOrbBasicControl().eraseStorage();
		sphero.stop();
	}
	
	private boolean checkSpheroConnection() {
		if (sphero == null) {
			Toast.makeText(activity, "No Sphero connected.", Toast.LENGTH_SHORT).show();			
			return false;
		}
		
		return true;
	}
	
	private void playSound() {
		MediaPlayer mp = MediaPlayer.create(activity, R.raw.sound);
        mp.setOnCompletionListener(new OnCompletionListener() {

            @Override
            public void onCompletion(MediaPlayer mp) {
                // TODO Auto-generated method stub
                mp.release();
            }

        });   
        mp.start();
	}

	private void showInfo() {
		AlertDialog.Builder builder = new AlertDialog.Builder(activity);
		
		builder.setTitle("Help");
		
		TextView text = new TextView(activity);
		text.setPadding(20, 20, 20, 20);
		text.setText(R.string.help);
		ScrollView scroller = new ScrollView(activity);
		scroller.addView(text);
		
		builder.setView(scroller);
		
		builder.setPositiveButton("Close",  new DialogInterface.OnClickListener() {
			public void onClick(DialogInterface dialog, int which) {
				
			}
		});
		
		final AlertDialog dialog = builder.create();
		dialog.show();
	}
	
	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		// Inflate the menu; this adds items to the action bar if it is present.
		getMenuInflater().inflate(R.menu.main, menu);
		return true;
	}

	@Override
	public boolean onTouch(View v, MotionEvent event) {
		
		if (event.getPointerCount() < 2) {
			final int X = (int) event.getX();
		    final int Y = (int) event.getY();
		    
		    switch (event.getAction() & MotionEvent.ACTION_MASK) {
		        case MotionEvent.ACTION_DOWN:
		        	deltaX = X;
		        	deltaY = Y;
		        	lastX = offsetX;
		        	lastY = offsetY;
		        	move = true;
		        	click = true;
		            break;
		        case MotionEvent.ACTION_MOVE:
		        	click = false;
		        	if (move) {
		        		offsetX = lastX + X - deltaX;
			        	offsetY = lastY + Y - deltaY;
		        	}
		            break;
		        case MotionEvent.ACTION_UP:
		        	if (click) {
		        		
		        		actionBrick = code.click(X, Y);
		        		
		        		if (actionBrick != null) {
		        			showContextMenu(R.menu.brick_action);
		        		}
		        	}
		        	break;
		        default: 
		        	break;
		    }
		    
		} else {
			final int X1 = (int) event.getX(event.getPointerId(0));
		    final int Y1 = (int) event.getY(event.getPointerId(0));
		    
		    final int X2 = (int) event.getX(event.getPointerId(1));
		    final int Y2 = (int) event.getY(event.getPointerId(1));
		    
		    final int xDist = Math.abs(X1 - X2);
        	final int yDist = Math.abs(Y1 - Y2);
        	final int dist = (int) Math.sqrt(xDist * xDist + yDist * yDist);
		    
		    switch (event.getAction() & MotionEvent.ACTION_MASK) {
		        case MotionEvent.ACTION_POINTER_DOWN:
		        	move = false;
		        	deltaTouchDistance = dist;
		            break;
		        case MotionEvent.ACTION_POINTER_UP:
		        	break;
		        case MotionEvent.ACTION_MOVE:
		        	scale += (int) ((dist - deltaTouchDistance) / 100);
		        	if (scale < 50) scale = 50;
		        	if (scale > 300) scale = 300;
		            break;
		        default: 
		        	break;
		    }
		}
	    
	    return true;
	}
	
	@Override
	public boolean onOptionsItemSelected(MenuItem item) {
	    switch (item.getItemId()) {
		    case R.id.action_new:
	        	newProgram();
	            return true;
		    case R.id.action_save:
	        	saveProgram();
	            return true;
		    case R.id.action_load:
	        	loadProgram();
	            return true;
		    case R.id.action_help:
	        	showInfo();
	            return true;
	        case R.id.action_run:
	        	executeCode();
	            return true;
	        case R.id.action_stop:
	        	abortExecuting();
	            return true;
	        case R.id.action_connect:
	        	Intent intent = new Intent(this, ConnectActivity.class);
	    	    startActivity(intent);
	            return true;
	        default:
	            return super.onOptionsItemSelected(item);
	    }
	}
	
	private boolean rawSaveProgram(final String fileName) {
		try {
	    	FileOutputStream outputStream;
	    	outputStream = activity.openFileOutput(fileName, activity.MODE_PRIVATE);
	        ObjectOutputStream os = new ObjectOutputStream(outputStream);
	        os.writeObject(code);
	        os.close();
	        return true;
	        
	    } catch(IOException e) {
	        e.printStackTrace();
	    }
		
		return false;
	}
	
	private void saveProgram() {
		AlertDialog.Builder builder = new AlertDialog.Builder(activity);
		
		builder.setTitle("Save program");
		
		builder.setMessage("Enter file name.");
		
		final EditText input = new EditText(activity);
		input.setHint("File name");
		builder.setView(input);
		
		builder.setPositiveButton("OK",  new DialogInterface.OnClickListener() {
			public void onClick(DialogInterface dialog, int which) {
				
			}
		});
		
		builder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
			public void onClick(DialogInterface dialog, int which) {
				
			}
		});
		
		final AlertDialog dialog = builder.create();
		dialog.show();
		
		dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(new View.OnClickListener() {            
			
			@Override
			public void onClick(View v) {
				if (input.getText().toString() != null && !input.getText().toString().isEmpty()) {
					if (rawSaveProgram(input.getText().toString())) {
						Toast.makeText(activity, "Program was successfuly saved.", Toast.LENGTH_SHORT).show();
					}
					dialog.dismiss();
				}
			}
		});
	}
	
	private boolean rawLoadProgram(final String fileName) {
		try {
			InputStream fi;
			fi = activity.openFileInput(fileName);
			ObjectInputStream input = new ObjectInputStream(fi);
			Code c = ((Code) input.readObject());
			c.debugBrickSequence();
			code = new Code();
			code.restoreCode(c.code);
			offsetX = 50;
			offsetY = 50;
			return true;
			
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (StreamCorruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		return false;
	}
	
	private void loadProgram() {
		AlertDialog.Builder builder = new AlertDialog.Builder(activity);
		
		builder.setTitle("My programs");
		
		activity.getFilesDir().list();
		final ListView listView = new ListView(activity);
		String[] list = activity.getFilesDir().list();
		final ArrayAdapter<String> listAdapter = new ArrayAdapter<String>(activity, android.R.layout.simple_list_item_1, list);
		listView.setAdapter(listAdapter);
		
		listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
		listView.setSelector(android.R.color.darker_gray);
		
		listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
			
			@Override
			public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
				selected = (String) listAdapter.getItem(position);
			}
		});
        
		builder.setView(listView);
		
		builder.setPositiveButton("Select", new DialogInterface.OnClickListener() {
			public void onClick(DialogInterface dialog, int which) {
				loadProgramAction(selected);
			}
		});
		
		builder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
			public void onClick(DialogInterface dialog, int which) {
			}
		});
		
		final AlertDialog dialog = builder.create();
		dialog.show();
	}
	
	private void loadProgramAction(final String file) {
		selected = "";
		if (file.isEmpty()) {
			return;
		}
		
		AlertDialog.Builder builder = new AlertDialog.Builder(activity);
		
		builder.setTitle("Program " + file);
		
		builder.setPositiveButton("Load",  new DialogInterface.OnClickListener() {
			public void onClick(DialogInterface dialog, int which) {
				if (rawLoadProgram(file)) {
					Toast.makeText(activity, "Program was successfuly loaded.", Toast.LENGTH_SHORT).show();
				}
			}
		});
		
		builder.setNeutralButton("Delete", new DialogInterface.OnClickListener() {
			public void onClick(DialogInterface dialog, int which) {
				String filePath = getFilesDir().getPath() + "/" + file;
				File file = new File(filePath);
				if (file.delete()) {
					Toast.makeText(activity, "Program was successfuly removed.", Toast.LENGTH_SHORT).show();
				}
			}
		});
		
		builder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
			public void onClick(DialogInterface dialog, int which) {
				
			}
		});
		
		final AlertDialog dialog = builder.create();
		dialog.show();
	}
	
	private void newProgram() {
		
		AlertDialog.Builder builder = new AlertDialog.Builder(activity);
		
		builder.setTitle("New program");
		
		builder.setMessage("Are you sure ? All chenges will be lost.");
		
		builder.setPositiveButton("OK",  new DialogInterface.OnClickListener() {
			public void onClick(DialogInterface dialog, int which) {
				code = new Code();
				offsetX = 50;
				offsetY = 50;
			}
		});
		
		builder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
			public void onClick(DialogInterface dialog, int which) {
				
			}
		});
		
		final AlertDialog dialog = builder.create();
		dialog.show();
	}
	
	private void fillBrickActionMenu(ContextMenu menu) {
		if (actionBrick.getType() != Brick.START 
			&& actionBrick.getType() != Brick.END
			&& actionBrick.getType() != Brick.ENDELSE
			&& actionBrick.getType() != Brick.ENDTHEN
			&& actionBrick.getType() != Brick.ENDIF
			&& actionBrick.getType() != Brick.ENDLOOP
			&& actionBrick.getType() != Brick.ENDWHILE
			&& actionBrick.getType() != Brick.ELSE
			&& actionBrick.getType() != Brick.THEN
		) {
			menu.add(1, R.id.action_delete, 3, R.string.delete);
		}
		if (actionBrick.getType() != Brick.END 
			&& actionBrick.getType() != Brick.ENDELSE
			&& actionBrick.getType() != Brick.ENDTHEN
			&& actionBrick.getType() != Brick.IF
		) {
			menu.add(1, R.id.action_add_after, 1, R.string.add_after);
		}
		if (actionBrick.getType() != Brick.END 
			&& actionBrick.getType() != Brick.ENDELSE
			&& actionBrick.getType() != Brick.ENDTHEN
			&& actionBrick.getType() != Brick.ENDIF
			&& actionBrick.getType() != Brick.ENDLOOP
			&& actionBrick.getType() != Brick.ENDWHILE
			&& actionBrick.getType() != Brick.ELSE
			&& actionBrick.getType() != Brick.THEN
			&& actionBrick.getType() != Brick.SOUND
			&& actionBrick.getType() != Brick.STOP
			&& actionBrick.getType() != Brick.START
		) {
			menu.add(1, R.id.action_edit, 1, R.string.edit);
		}
	}
	
	@Override  
    public void onCreateContextMenu(ContextMenu menu, View v,ContextMenuInfo menuInfo) {  
		
		if (menuShow == R.menu.brick_action) {
			fillBrickActionMenu(menu);
		}
		super.onCreateContextMenu(menu, v, menuInfo);
	    MenuInflater inflater = getMenuInflater();
	    inflater.inflate(menuShow, menu);  
    }
	
	@Override
	public boolean onContextItemSelected(MenuItem item) {
		
		switch (item.getItemId()) {
			case R.id.action_add_after:
				addBrickIndex = code.indexOfBrick(actionBrick) + 1;
				view.post(new Runnable() {
			        @Override
			        public void run() {
			        	showContextMenu(R.menu.bricks);
			        }
			    });
				return true;
			case R.id.action_edit:
				if (actionBrick.getType() == Brick.IF) {
					((IfBrick) actionBrick).edit(activity);
				} else if (actionBrick.getType() == Brick.LOOP || actionBrick.getType() == Brick.WHILE) {
					((LoopBrick) actionBrick).edit(activity);
				} else {
					((SimpleBrick) actionBrick).edit(activity);
				} 
				return true;
			case R.id.action_delete:
				code.removeBrick(actionBrick);
				return true;
			case R.id.brick_roll:
				code.addBrick(new SimpleBrick(Brick.ROLL, activity), addBrickIndex);
				return true;
			case R.id.brick_stop:
				code.addBrick(new SimpleBrick(Brick.STOP), addBrickIndex);
				return true;
			case R.id.brick_color:
				code.addBrick(new SimpleBrick(Brick.COLOR, activity), addBrickIndex);
				return true;
			case R.id.brick_led:
				code.addBrick(new SimpleBrick(Brick.LED, activity), addBrickIndex);
				return true;
			case R.id.brick_delay:
				code.addBrick(new SimpleBrick(Brick.DELAY, activity), addBrickIndex);
				return true;
			case R.id.brick_print:
				code.addBrick(new SimpleBrick(Brick.PRINT, activity), addBrickIndex);
				return true;
			case R.id.brick_sound:
				code.addBrick(new SimpleBrick(Brick.SOUND), addBrickIndex);
				return true;
			case R.id.brick_variable:
				code.addBrick(new SimpleBrick(Brick.VARIABLE, activity), addBrickIndex);
				return true;
			case R.id.brick_loop:
				if (code.getBrickLoopDepth(actionBrick) < 3) {
					code.addBrick(new LoopBrick(Brick.LOOP, activity), addBrickIndex);
				} else {
					Toast.makeText(activity, "Max loop depth is reached.", Toast.LENGTH_SHORT).show();
				}
				return true;
			case R.id.brick_while:
				code.addBrick(new LoopBrick(Brick.WHILE, activity), addBrickIndex);
				return true;
			case R.id.brick_if:
				code.addBrick(new IfBrick(activity), addBrickIndex);
				return true;
			default:
				return super.onContextItemSelected(item);
		}
	}
	
	private void showContextMenu(int res) {
		
		menuShow = res;
		view.showContextMenu();
	}
	
	public class RenderView extends SurfaceView implements Runnable {
		
		Thread thread = null;
		SurfaceHolder holder;
		boolean running = false;
		
		public RenderView(Context context) {
			super(context);
			holder = getHolder();
		}
		
		public void run() {
			while(running) {
				if(!holder.getSurface().isValid()) {
					continue;
				}
				
				Canvas canvas = holder.lockCanvas();
				
				code.renderCode(canvas, offsetX, offsetY, scale);
				
				holder.unlockCanvasAndPost(canvas);
			}
		}
		
		public void pause() {
			running = false;
			
			while(true) {
				try {
					thread.join();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				break;
			}
			
			thread = null;
		}
		
		public void resume() {
			running = true;
			thread = new Thread(this);
			thread.start();
		}
	}

}
